package org.sunshine.miracle.ui;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TreeAdapter;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.sunshine.miracle.dao.DaoUtils;
import org.sunshine.miracle.dao.FilesManager;
import org.sunshine.miracle.model.VirtualFile;

import java.io.File;
import java.util.List;

public class FileTree {

    private static final Log LOGGER = LogFactory.getLog(FileTree.class);

    private static final String CHILDREN_LOADED_KEY = "children_loaded";
    private static final String FILE_KEY = "file";


    private org.eclipse.swt.widgets.Tree fileTree;
    private TreeEditor fileTreeEditor;
    private Shell shell;

    private final FilesManager filesManager;

    private MenuItem newVirualSubfolderMenuItem;
    private MenuItem addLocationMenuItem;
    private MenuItem deleteMenuItem;
    private MenuItem renameMenuItem;
    private FileInfoPanel infoPanel;


    public FileTree(Composite parent, Shell shell) {
//        fileTree = new Tree(parent, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI){
//            public void addMenuDetectListener(MenuDetectListener listener) {
//                System.out.println("addMenuDetectListener");
//                super.addMenuDetectListener(null);
//            }
//        };
        fileTree = new org.eclipse.swt.widgets.Tree(parent, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI);
        this.shell = shell;
        filesManager = DaoUtils.getFilesManager();

        assignTreeEditor();

//        fileTree.addMenuDetectListener(new MenuDetectListener() {
//            public void menuDetected(MenuDetectEvent e) {
//                rebuildTreeMenu();
//            }
//        });

        fileTree.addMouseListener(new MouseAdapter() {
            public void mouseUp(MouseEvent e) {
                // Deselect all if user clicks not on item.
                TreeItem treeItem = fileTree.getItem(new Point(e.x, e.y));
                if (treeItem == null) fileTree.deselectAll();
                disposeTreeEditor();
            }
        });

        fileTree.addTreeListener(new TreeAdapter() {
            public void treeExpanded(TreeEvent event) {
                final TreeItem item = (TreeItem) event.item;
                VirtualFile file = (VirtualFile) item.getData(FILE_KEY);
                if (file != null) {
                    if (file.isFolder()) {
                        if (file.isVirtual()) {
                            item.setImage(ImageCache.getImage(ImageCache.OPEN_VIRTUAL_FOLDER));
                        } else {
                            item.setImage(ImageCache.getImage(ImageCache.OPEN_REAL_FOLDER));
                        }
                    }

                    refreshTreeItemChildren(item);
                }

            }

            public void treeCollapsed(TreeEvent event) {
                final TreeItem item = (TreeItem) event.item;
                VirtualFile file = (VirtualFile) item.getData(FILE_KEY);
                if (file != null) {
                    if (file.isFolder()) {
                        if (file.isVirtual()) {
                            item.setImage(ImageCache.getImage(ImageCache.CLOSED_VIRTUAL_FOLDER));
                        } else {
                            item.setImage(ImageCache.getImage(ImageCache.CLOSED_REAL_FOLDER));
                        }
                    }
                }
            }
        });

        fileTree.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                if (infoPanel != null) {
                    if (fileTree.getSelectionCount() == 1) {
                        TreeItem treeItem = fileTree.getSelection()[0];
                        VirtualFile file = (VirtualFile) treeItem.getData(FILE_KEY);
                        infoPanel.setFileInfo(file);
                    } else {
                        infoPanel.clearInfo();
                    }
                }
            }
        });

        refreshTreeRoot();
        buildFilesTreeMenu();

    }


    /** Tree editor is needed to allow cell edititing. */
    public void assignTreeEditor() {

        fileTreeEditor = new TreeEditor(fileTree);

        //The editor must have the same size as the cell and must
        //not be any smaller than 50 pixels.
        fileTreeEditor.horizontalAlignment = SWT.RIGHT;
        fileTreeEditor.grabHorizontal = true;
        fileTreeEditor.minimumWidth = 50;


        fileTree.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                TreeItem editorItem = fileTreeEditor.getItem();
                if (editorItem != null && !e.item.equals(editorItem)) {
                    disposeTreeEditor();
                }
            }

        });

    }

    /** Stop cell edit */
    private void disposeTreeEditor() {
        Control editor = fileTreeEditor.getEditor();
        if (editor != null) editor.dispose();
    }


    /** Construct tree context menu */
    private void buildFilesTreeMenu() {
        Menu treeContextMenu = SWTUtils.createMenu(fileTree);
        fileTree.setMenu(treeContextMenu);

        newVirualSubfolderMenuItem = SWTUtils.createMenuItem(treeContextMenu, SWT.PUSH);
        newVirualSubfolderMenuItem.setText(StringBundle.getResourceString("menu.new_vitual_subfolder"));
        newVirualSubfolderMenuItem.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                addVirtualSubfolder();
            }
        });

        MenuItem subfolderSeparatorMenuItem = new MenuItem(treeContextMenu, SWT.SEPARATOR);

        addLocationMenuItem = SWTUtils.createMenuItem(treeContextMenu, SWT.PUSH);
        addLocationMenuItem.setText(StringBundle.getResourceString("menu.add_location"));
        addLocationMenuItem.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                LOGGER.debug("Adding location...");
                addFilesAction();
            }
        });

        deleteMenuItem = SWTUtils.createMenuItem(treeContextMenu, SWT.PUSH);
        deleteMenuItem.setText(StringBundle.getResourceString("menu.delete"));
        deleteMenuItem.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                deleteSelectedFiles();
            }
        });

        renameMenuItem = SWTUtils.createMenuItem(treeContextMenu, SWT.PUSH);
        renameMenuItem.setText(StringBundle.getResourceString("menu.rename"));
        renameMenuItem.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                if (fileTree.getSelectionCount() != 1) return;
                renameTreeItem(fileTree.getSelection()[0]);
            }
        });

    }


    private void rebuildTreeMenu() {
        if (fileTree.getSelectionCount() > 1) {
            newVirualSubfolderMenuItem.setEnabled(false);
            addLocationMenuItem.setEnabled(false);
            renameMenuItem.setEnabled(false);
        } else if (fileTree.getSelectionCount() == 0) { // allow only to add virtual folder as a root.
            newVirualSubfolderMenuItem.setEnabled(true);
            addLocationMenuItem.setEnabled(false);
            deleteMenuItem.setEnabled(false);
            renameMenuItem.setEnabled(false);
            return;
        } else {
            newVirualSubfolderMenuItem.setEnabled(true);
            addLocationMenuItem.setEnabled(true);
            deleteMenuItem.setEnabled(true);
            renameMenuItem.setEnabled(true);
        }

        TreeItem[] items = fileTree.getSelection();
        for (TreeItem item : items) {
            VirtualFile file = (VirtualFile) item.getData(FILE_KEY);
            Assert.notNull(file, "Tree item contains no file.");
            if (!file.isVirtual()) {
                newVirualSubfolderMenuItem.setEnabled(false);
            }
            if (!file.isFolder()) {
                newVirualSubfolderMenuItem.setEnabled(false);
                addLocationMenuItem.setEnabled(false);
                deleteMenuItem.setEnabled(true);
                renameMenuItem.setEnabled(true);
            }


        }


    }


    private void refreshTreeItemChildren(TreeItem root) {
        refreshTreeItemChildren(root, false);
    }

    private void refreshTreeItemChildren(TreeItem root, boolean forceRefresh) {

        Object loaded = root.getData(CHILDREN_LOADED_KEY);
        if (!forceRefresh && loaded != null) return;

        LOGGER.debug("loading folder children...");

        root.removeAll();

        VirtualFile virtualFile = (VirtualFile) root.getData(FILE_KEY);
        List<VirtualFile> files = DaoUtils.getFilesManager().getVirtualFiles(virtualFile.getId());
        for (VirtualFile file : files) {
            TreeItem newItem = new TreeItem(root, SWT.NONE);
            assignFileToTreeItem(file, newItem);
        }
        root.setData(CHILDREN_LOADED_KEY, true);
    }


    private void refreshTreeRoot() {
        List<VirtualFile> files = DaoUtils.getFilesManager().getVirtualFiles(null);
        for (VirtualFile file : files) {
            TreeItem newItem = new TreeItem(fileTree, SWT.NONE);
            assignFileToTreeItem(file, newItem);
        }
    }


    private void assignFileToTreeItem(VirtualFile file, TreeItem newItem) {
        newItem.setText(file.getName());

        if (file.isFolder() && file.getChildrenCount() > 0) { // add empty tree item to show expand icon
            new TreeItem(newItem, SWT.NONE);
        }

        if (file.isFolder()) {
            if (file.isVirtual()) {
                newItem.setImage(ImageCache.getImage(ImageCache.CLOSED_VIRTUAL_FOLDER));
            } else {
                newItem.setImage(ImageCache.getImage(ImageCache.CLOSED_REAL_FOLDER));
            }
        } else {
            Image imageForFile = ImageCache.getImageForFile(file);
            newItem.setImage(imageForFile);
        }

        newItem.setData(FILE_KEY, file);
    }

    private void addFilesAction() {
        FileDialog dialog = new FileDialog(shell, SWT.MULTI);
        dialog.setText(StringBundle.getResourceString("dialog.add_location.title"));
        String result = dialog.open();
        if (!StringUtils.hasText(result)) return;
        LOGGER.debug("Adding files...");
        String[] fileNames = dialog.getFileNames();
        addFiles(dialog.getFilterPath(), fileNames);
    }

    private void addVirtualSubfolder() {
        if (fileTree.getSelectionCount() > 1) return;

        VirtualFile newFile = new VirtualFile();
        TreeItem newFolderItem;
        if (fileTree.getSelectionCount() == 0) { // add root folder
            newFolderItem = new TreeItem(fileTree, SWT.NONE);
        } else {
            TreeItem rootTreeItem = fileTree.getSelection()[0];
            VirtualFile file = (VirtualFile) rootTreeItem.getData(FILE_KEY);
            Assert.isTrue(file.isVirtual() && file.isFolder(), "We cannot add virtual folder to real folder or file.");
            refreshTreeItemChildren(rootTreeItem, true);

            newFile.setParentId(file.getId());

            newFolderItem = new TreeItem(rootTreeItem, SWT.NONE);
            rootTreeItem.setExpanded(true);
        }

        newFolderItem.setData(FILE_KEY, newFile);
        newFolderItem.setText(StringBundle.getResourceString("filetree.new_folder"));
        newFolderItem.setImage(ImageCache.getImage(ImageCache.CLOSED_VIRTUAL_FOLDER));

        renameTreeItem(newFolderItem);
    }

    private void deleteSelectedFiles() {
        if (fileTree.getSelectionCount() < 1) return;
        try {
            FilesManager filesManager = DaoUtils.getFilesManager();
            TreeItem[] treeItems = fileTree.getSelection();
            for (int i = 0; i < treeItems.length; i++) {
                TreeItem item = treeItems[i];
                VirtualFile file = (VirtualFile) item.getData(FILE_KEY);
                filesManager.deleteFolder(file.getId());
                item.dispose();
            }

        } catch (Exception ex) {
            LOGGER.error("Error when deleting files.", ex);
            SWTUtils.showErrorMessageBox(shell, "Cannot delete selected item(s).");
        }
    }

    private void addFiles(String directoryPath, String[] files) {

        if (fileTree.getSelectionCount() != 1) return;
        TreeItem selectedItem = fileTree.getSelection()[0];
        VirtualFile parentVirtualFile = (VirtualFile) selectedItem.getData(FILE_KEY);
        Assert.notNull(parentVirtualFile, "Tree item contains no file.");

        TreeItem treeItem = null;

        for (String path : files) {
            File file = new File(directoryPath + File.separator + path);
            if (!file.exists()) {
                LOGGER.info("Selected file " + file.getAbsolutePath() + " does not exist.");
                continue;
            } else if (file.isDirectory()) {
                LOGGER.error("We can only add a file but not a directory.");
                continue;
            }

            LOGGER.debug("Adding file: " + file.getAbsolutePath());

            VirtualFile virtualFile = new VirtualFile();
            virtualFile.setParentId(parentVirtualFile.getId());
            virtualFile.setFolder(file.isDirectory());
            virtualFile.setVirtual(false);
            virtualFile.setName(file.getName());
            virtualFile.setAbsolutePath(file.getAbsolutePath());

            boolean contains = filesManager.contains(parentVirtualFile.getId(), virtualFile.getName(), virtualFile.isFolder());
            if (contains) { // if parent folder already contains file with such name, show error message and return.
                showAlreadyExistsMessageBox(virtualFile.getName());
                return;
            }
            filesManager.saveVirtualFolder(virtualFile);

            treeItem = new TreeItem(selectedItem, SWT.NONE);

            assignFileToTreeItem(virtualFile, treeItem);

        }
        if (treeItem != null)
            fileTree.showItem(treeItem);
    }

    private void showAlreadyExistsMessageBox(String childName) {
        StringBuilder message = new StringBuilder("Cannot insert file ").append(childName).append(" because file with such name already exists.");
        LOGGER.info(message.toString());
        SWTUtils.showErrorMessageBox(shell, message.toString());
    }


    private void renameTreeItem(TreeItem item) {
        // Clean up any previous editor control
        disposeTreeEditor();

        // The control that will be the editor must be a child of the Tree
        Text newEditor = new Text(fileTree, SWT.NONE);
        newEditor.setText(item.getText());
        newEditor.selectAll();
        newEditor.setFocus();

        newEditor.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {

                if (e.keyCode == SWT.ESC) {
                    disposeTreeEditor();
                }

                if (e.keyCode == SWT.CR) {
                    updateFileName();
                    disposeTreeEditor();
                }
            }
        });

        newEditor.addFocusListener(new FocusListener() {
            public void focusGained(FocusEvent e) {
            }

            public void focusLost(FocusEvent e) {
                updateFileName();
            }
        });

        newEditor.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                Text text = (Text) fileTreeEditor.getEditor();
                fileTreeEditor.getItem().setText(text.getText());
            }
        });
        fileTreeEditor.setEditor(newEditor, item);
    }


    private void updateFileName() {
        TreeItem editedItem = fileTreeEditor.getItem();
        Text text = (Text) fileTreeEditor.getEditor();
        String newName = text.getText();
        try {


            VirtualFile file = (VirtualFile) editedItem.getData(FILE_KEY);
            if (file == null) throw new IllegalStateException("Edited cell contains no file inside.");

            boolean contains = filesManager.contains(file.getParentId(), newName, file.isFolder());
            if (contains) {
                showAlreadyExistsMessageBox(newName);
            }

            file.setName(newName);
            DaoUtils.getFilesManager().saveVirtualFolder(file);

            editedItem.setText(file.getName());

        } catch (Exception e) {
            LOGGER.error("Exception when setting new file name.", e);

            SWTUtils.showErrorMessageBox(shell, "Cannot set such name. Exception: \n" + e.getMessage());

            if (editedItem != null && !editedItem.isDisposed()) {
                editedItem.dispose();
            }
        }
    }


    public void setInfoPanel(FileInfoPanel infoPanel) {
        this.infoPanel = infoPanel;
    }
}
